﻿using InDepth2Demo.Model;
using System;
using System.Collections.Generic;

namespace InDepth2Demo.Chapter3
{
    // C# 图解教程中的 协变与逆变

    delegate T Factory<out T>();

    class Animal { public int Legs = 4; }       //基类
    class Dog : Animal { }                       //派生类
    class Cat : Animal { }                       //派生类

    internal class VarianceDemo
    {
        static Dog MakeDog()            // 匹配 Factory 委托的方法
        {
            return new Dog();
        }

        static void Call()
        {
            Factory<Dog> dogMaker = MakeDog;            //创建委托对象
            //Factory<Animal> animalMaker = dogMaker;          //尝试赋值委托对象   (编译器提示错误)
            //Console.WriteLine(animalMaker().Legs.ToString());

            // 原本为 delegate T Factory<T>();
            // 当加上 out 关键字之后 
            // delegate T Factory<out T>();
            Factory<Animal> animalMaker = dogMaker;
            Console.WriteLine(animalMaker().Legs.ToString());

            // ∴ 泛型委托支持协变。（注：数组也是协变的，‌但泛型并不支持协变）
        }

        /// <summary>
        /// 1.泛型为何不支持协变性
        /// </summary>
        public static void Call2()
        {
            // 有效(在编译时)
            Animal[] animals = new Cat[5];                  // animals 实际引用的对象是一个 Cat[]
            animals[0] = new Dog();                         // 在运行时报错：System.ArrayTypeMismatchException:“尝试访问类型与数组不兼容的元素。”

            // 无效
            //List<Animal> animalList = new List<Cat>();    // 这一句就已经编译不通过了
            //animalList.Add(new Dog());                    // 这一句是可以运行的 （一个 Dog 类当然能放到任何动物列表中）


            // 这里尝试的是
            List<Animal> animalList = new List<Animal>();
            animalList.Add(new Dog());
            Console.WriteLine(animalList[0].Legs);
        }

        // 再测试一下 泛型是不是协变
        static void Call3()
        {
            GenericClass<Dog> dog = new GenericClass<Dog>();

            //GenericClass<Animal> animal = dog;    
            //提示报错：无法将类型 "InDepth2Demo.Model.GenericClass<InDepth2Demo.Chapter3.Dog>" 隐式转换为 "InDepth2Demo.Model.GenericClass<InDepth2Demo.Chapter3.Animal>" 

            //证明：不能协变。
        }

        interface IShape { }
        class Circle : IShape { }
        class Rectangle : IShape { }

        interface IDrawing
        {
            IEnumerable<IShape> Shapes { get; }
        }
        class MondrianDrawing : IDrawing
        {
            public IEnumerable<IShape> Shapes { get; }

            List<Rectangle> Rectangles { get; }
        }
        class SeuratDrawing : IDrawing
        {
            public IEnumerable<IShape> Shapes => throw new NotImplementedException();

            List<Circle> Circles { get; }
        }

        // 2. 协变性在什么时候有用
        // IEnumerator<T> 和(相关的) IEnumerable<T>  (这俩都有 out 关键字) 它们是泛型协变最典型的示例。
        // 它们共同描述一个值的序列，每一个值都与兼容，因此可以写成这样  T currentValue = iterator.Current;
        static void Call4()
        {
            // T currentValue = iterator.Current;
            IEnumerable<Animal> animals = GetCats();

            // 好的这个单元没看懂
        }

        static IEnumerable<Cat> GetCats()
        {
            return new Cat[5];
        }

        // 3. 逆变性在什么地方有用
        static void Call5()
        {
            // 使用协变性，可以将 SomeType<Circle> 转换为 SomeType<IShape> (本例中的SomeType为IEnumerable<T>)。
            // 而逆变性则是进行反向转换--从 SomeType<Ishape> 转换为 SomeType<Circle>。

            IComparer<IShape> areaComparer = new AreaComparer();
            List<Circle> circles = new List<Circle>();
            circles.Add(new Circle());
            circles.Add(new Circle());
            circles.Sort(areaComparer);

            //List<Circle> circles = new List<Circle>();

            // public void Sort(IComparer<Circle> comparer)
            circles.Sort();
        }

        // 使用泛型辅助类解决逆变性缺乏问题
        class ComparsionHelper<TBase, TDerived> : IComparer<TDerived>
            where TDerived : TBase                                      // 适当地约束类型参数
        {
            private readonly IComparer<TBase> comparer;                 // 保存原始的比较器

            public ComparsionHelper(IComparer<TBase> comparer)
            {
                this.comparer = comparer;
            }

            public int Compare(TDerived x, TDerived y)
            {
                return comparer.Compare(x, y);                          // 使用隐式转换来调用比较器
            }
        }
        // 总结：虽然传入的派生类，但其实里面有一个 基类的比较器，实际用的是基类的比较器。


        // 第二个对策是使面积比较类成为一个泛型类, 并添加一个派生约束，使它能比较相同类型的任何两个值。只要该类型实现了 IShape。
        class AreaComparer<T> : IComparer<T> where T : IShape
        {
            public int Compare(T x, T y)
            {
                throw new NotImplementedException();
            }
        }
        //简便起见，可以保留非泛型类，让它继承这个泛型类：
        class AreaComparer : AreaComparer<IShape>
        { }
    }
}
